package com.aps.algorithm.heuristic.genetic;


import cn.hutool.core.collection.CollUtil;
import com.aps.algorithm.core.AlgorithmParameters;
import com.aps.algorithm.heuristic.GeneticAlgorithmStrategy;
import com.aps.algorithm.heuristic.GeneticStrategyParams;
import com.aps.algorithm.heuristic.genetic.dto.ApsOperationDto;
import com.aps.common.constant.Constants;
import com.aps.common.entity.ApsDayOfWeek;
import com.aps.common.entity.ApsInterval;
import com.aps.common.model.genetic.Equipment;
import com.aps.common.model.genetic.Order;
import com.aps.common.model.genetic.OrderProcess;
import com.aps.common.model.genetic.ProcessInterval;
import com.aps.framework.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.chrono.ChronoLocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 化工行业
 * @author: jsy
 * @time: 2025/9/25 15:40
 */
@Slf4j
@Component
public class GeneticChemicalAlgorithm extends GeneticAlgorithmStrategy {
    @Override
    public String getStrategyType() {
        return "chemical";
    }

    @Override
    public void setParameters(AlgorithmParameters parameters) {
        this.parameters = parameters; // 直接赋值父类的parameters字段
    }

    /**
     * @description: 计算染色体适应度
     * @author: jsy
     * @time: 2025/9/23 17:05
     */

    @Override
    public double calSingleChromosome(GeneticStrategyParams params) {
        //记录每个订单下一个工序的可执行的时间
        Map<String, LocalDateTime> lastOperationEndTime = new HashMap<>();
        for (ApsOperationDto apsOp : params.getGeneSequence()) {
            if (((LocalDateTime) this.parameters.getParameter(Constants.GLOBAL_START_TIME)).isAfter(params.getOrderMap().get(apsOp.getOrderProcess().getOrderId()).getDueDate())) {
                throw new BusinessException("全局开始时间不能晚于交付日期");
            }
            LocalDateTime orderLastEndTime = lastOperationEndTime.getOrDefault(apsOp.getOrderProcess().getOrderId(), (LocalDateTime) this.parameters.getParameter(Constants.GLOBAL_START_TIME));
            //选择合适的设备
            Equipment selectedMachine = selectBestMachine(apsOp.getOrderProcess(), orderLastEndTime, params.getEquipmentMap());
            //TODO 这里的情况 1、这道工序就不需要设备 2、项目校验错误（业务代码问题，目前项目会校验 如果工序配置类设备类型，那么必须选择设备） 。
            if (selectedMachine == null) {
                throw new BusinessException("无可用设备"); // 无可用设备，适应度为0
            }
            //计划开始时间
            LocalDateTime earliestPossibleStart = selectedMachine.getNextAvailableTime().isAfter(orderLastEndTime) ?
                    selectedMachine.getNextAvailableTime() : orderLastEndTime;
            LocalDateTime currentTime = earliestPossibleStart;
            //currentTime 加上工序准备时间 如果不在工作时间范围内的顺延到下一个工作时间
            //实际开始时间
            currentTime = calculateEndTime(currentTime, apsOp.getOrderProcess().getOperationPreparedTime());
            for (int i = 0; i < params.getOrderMap().get(apsOp.getOrderProcess().getOrderId()).getQuantity(); i++) {
                //计算工序的结束时间
                LocalDateTime productEndTime = calculateEndTime(currentTime, apsOp.getOrderProcess().getUnitProcessingTime());
                //计算时间区间
                ProcessInterval processInterval = new ProcessInterval(apsOp.getOrderProcess().getId(), currentTime, productEndTime, i + 1, selectedMachine.getId());
                //添加基因时间区间列表
                apsOp.getOrderProcess().addProcessInterval(processInterval);
                //更新这个基因的开始时间
                currentTime = productEndTime;
            }
            //把处理过的工序添加到订单中
            params.getOrderMap().get(apsOp.getOrderProcess().getOrderId()).getProcessList().add(apsOp.getOrderProcess());
            if (apsOp.getOrderProcess().getProcessIntervalList().size() <= 0) {
                throw new BusinessException("工序没有时间区间");
            }
            LocalDateTime startTime = null;
            //基因的结束时间
            LocalDateTime endTime = null;
            if (CollUtil.isNotEmpty(apsOp.getOrderProcess().getProcessIntervalList())) {
                //基因的开始时间
                startTime = apsOp.getOrderProcess().getStartTime();
                //基因的结束时间
                endTime = apsOp.getOrderProcess().getEndTime();
            }

            //TODO 《设备冲突》需要搞一个方法出来
            // 设备冲突时，这道工序，以及后续工序 的开始时间、结束时间 应该有变化 。
            // 当前selectedMachine 已经是该工序对应设备类型 最早的设备了.
            // 工序开始时间、结束时间计算：该工序的开始时间应该是 设备的结束时间 + 工序准备时长。 结束时间为 工序的标准时长 + 开始时间。
            // 设备使用时段对照表中，该设备 对应的使用时段应该 新增一条使用时间记录： 当前工序的开始时间，结束时间。
            // 需要重新推算一下 当前染色体对应 该工序后续工序的 开始时间和结束时间。（上面代码做了）


            // 检查设备冲突
            if (selectedMachine.hasConflict(startTime, endTime)) {
//                op.setHasMachineConflict(true);
                params.setHasEquipmentConflicts(true);
            }

            // TODO 这里超期 可以返回一个数值，例如之前讨论过 超期多少天 超期越多 分数越小 。 因为可能就是订单数量比较多导致 或者 单个订单数量很大 、 或者交付日期 很短 导致超期
            //是否超过交付日期。超过的适应度为0 直接返回 代表整个染色体不可用
//            if (endTime.isAfter(params.getOrderMap().get(apsOp.getOrderProcess().getOrderId()).getDueDate())) {
//                throw new BusinessException("交付日期错误");
//            }
            //选中的机器更新下一个可用的时间
            selectedMachine.setNextAvailableTime(endTime);
            if (startTime.isAfter(endTime)) {
                throw new IllegalArgumentException("开始时间不能晚于结束时间");
            }
            //更新这个设备时间区间
            selectedMachine.addEquipmentInterval(apsOp.getOrderProcess().getId(), startTime, endTime, apsOp.getOrderProcess().getSequence(), selectedMachine.getId());
            //更新这个订单的下一个工序的开始时间
            lastOperationEndTime.put(apsOp.getOrderProcess().getOrderId(), endTime);
        }
        for (ApsOperationDto apsOp : params.getGeneSequence()) {
            if (apsOp.getOrderProcess().getProcessIntervalList().size() <= 0) {
                throw new BusinessException("工序没有时间区间");
            }
        }
        //机器存在冲突
        if (params.hasEquipmentConflicts()) {
            return 0.1;
        }
        // 计算适应度值 1、交货期延期惩罚和提前完成 2、工序准备惩罚 3、加班成本惩罚
        double fitness = calculateFitness(params.getOrderMap(), this.parameters);
        return fitness;
    }

    /**
     * @description: 计算适应度值
     * @author: jsy
     * @time: 2025/10/23 22:10
     */
    public double calculateFitness(Map<String, Order> orders, AlgorithmParameters parameters) {
        // 1. 基础分
        double baseScore = 1.0;

        // 2. 计算各维度惩罚项
        double delayPenalty = calculateDelayOrEarlyPenalty(orders);// 交货期延期惩罚和提前完成
//        double switchPenalty = calculateProcessPenalty(orders);// 工序准备惩罚
        double overtimePenalty = calculateOvertimePenalty(orders);// 加班成本惩罚

        // 3. 总适应度 = 基础分 - 所有惩罚项加权和
        return Math.max(0, Math.min(baseScore + delayPenalty + overtimePenalty, 1.0)); // 适应度最低为0
    }

    /**
     * @description: 交货期延期惩罚、提前完工加分
     * 惩罚系数：WEIGHT_DELAY
     * @author: jsy
     * @time: 2025/10/23 22:38
     */
    private double calculateDelayOrEarlyPenalty(Map<String, Order> orders) {
        double delay = 0.0;
        long minutesDelay = 0;
        long minutesEarly = 0;
        for (Order order : orders.values()) {
            // 1、计算时间间隔（Duration）
            Duration duration = Duration.between(order.getLastProcess().getEndTime(), order.getDueDate());
            //2、转换为秒数
            long minutes = duration.toMinutes();
            //判断seconds是否大于0 大于0 表示这个订单提前完成 。小于0 表示延期完成
            if (minutes >= 0) {
                //提前完成 加分
                minutesEarly += minutes;
            } else if (minutes < 0) {
                //延迟完成 减分
                minutesDelay += minutes;
            }
        }
        // 提前完成 加分
        delay += calculateEarlyBonus(minutesEarly, 0.05, 7200, 21600, 0.00000556);
        // 延迟完成 减分
        delay -= calculateDelayPenalty(-minutesDelay, 0.6, 14400, 43200, 0.0000333);
        return delay;
    }


//    /**
//     * @description: 工序准备惩罚
//     * 惩罚系数：WEIGHT_SWITCH
//     * @author: jsy
//     * @time: 2025/10/23 22:38
//     */
//    private double calculateProcessPenalty(Map<String, Order> orders) {
//        double delay = 0.0;
//        for (Order order : orders.values()) {
//            for (OrderProcess orderProcess : order.getProcessList()) {
//                //工序准备时间 转换成秒
//                double v = orderProcess.getOperationPreparedTime() * 60;
//                //累加工序准备时间
//                delay += v;
//            }
//        }
//
//        return delay * WEIGHT_SWITCH;
//    }


    /**
     * @description: 加班成本惩罚：超出正常工作时间的部分
     * 惩罚系数：WEIGHT_OVERTIME
     * @author: jsy
     * @time: 2025/10/23 22:38
     */
    private double calculateOvertimePenalty(Map<String, Order> orders) {
        double delay = 0.0;
        long secondsOvertime = 0;
        for (Order order : orders.values()) {
            // 1、计算加班的时长（单位：秒）
            for (OrderProcess orderProcess : order.getProcessList()) {
                for (ProcessInterval processInterval : orderProcess.getProcessIntervalList()) {
                    // 获取当天的时段配置（正常时段+加班时段）
                    ApsDayOfWeek apsDayOfWeek = getWorkInfoMap(processInterval.getStartTime());
                    // 获取加班时段
                    ApsInterval apsIntervalOver = apsDayOfWeek.getApsIntervalOver();
                    if (apsIntervalOver == null) {
                        continue;
                    }
                    if(processInterval.getEndTime().toLocalTime().isBefore(apsIntervalOver.getStartTime())){
                        continue;
                    }
                    // 计算加班开始到结束时间间隔（Duration）
                    Duration duration = Duration.between(apsIntervalOver.getStartTime(), processInterval.getEndTime());
                    //转换成分钟
                    secondsOvertime += duration.toMinutes();
                }
            }
        }
        //计算加班成本惩罚
        if (secondsOvertime > 0) {
            delay -= calculateOvertimePenalty(secondsOvertime, 0.35, 1200, 3000, 0.000233);
        }
        return delay;
    }
}
